#include <kutils.h>
#include <string.h>
#include "memory.h"
#include "fat.h"
#include "vfs.h"
#include "ata.h"
#include "display.h"

s32int fat_cluster_is_eoc(u32int active_cluster, fat_entry_t *entry);
u32int fat_next_in_chain(u32int active_cluster, fat_entry_t *entry);
s32int fat_entry_exists_at(u8int *entry);
s32int fat_entry_is_longfn(u8int *entry);
s32int fat_store_name_from_buffer(u8int *entry);
s32int fat_apply_lfn(fat_entry_t *entry);
s32int fat_fill_data_from_buffer(u8int *buffer, fat_entry_t *entry);

fat_entry_t *root_entries[4];//4 fat roots, one for each drive
u32int fat_root_exists[4];
fat_file_handle_t *root_handle;

u32int fat_table_cache[sizeof(u8int) * 512]; 
u32int fat_table_cached_cluster = 0xFFFFFFFF;//invalid cluster number

u32int first_data_sector;
u32int first_fat_sector;

s8int tmp_name[128];

//creates a fat filesystem on top of the given drive and partition. 
//returns 1 for normal exit, and 0 for error exit
s32int fat_mount(fs_node_t *root_node, u8int *device_path)
{
	fs_node_t *dev_node = open_fs((s8int*)device_path);

	if(!dev_node)
	{
		write_string("FAT: Device not found.\n");
		return 0;
	}

	if(!root_node)
	{
		write_string("FAT: Null root node.\n");
		return 0;
	}

	s32int i = 0;
	
	//*****************************************************************//
	//***** verify that the drive contains a valid fat filesystem *****//
	//*****************************************************************//
	fat_BS_t *fat_boot = (fat_BS_t*)kmalloc(sizeof(fat_BS_t));
	
	seek_fs(dev_node, 0);
	if(!read_fs(dev_node, sizeof(fat_BS_t), (u8int*)fat_boot))
	{
		write_string("FAT: Read request for the device failed.\n");
		kfree(fat_boot);
		return 0;
	}
	
	//determine the type of fat file system present, if any
	

	//fill in some essential data about the filesystem
	u8int fat_type = 0;
	first_data_sector = 
			(u32int)(fat_boot->reserved_sector_count + (fat_boot->table_count * fat_boot->table_size_16));
	first_fat_sector = fat_boot->reserved_sector_count;
	u32int data_sectors = 
			(u32int)(fat_boot->total_sectors_16 - 
			(
				fat_boot->reserved_sector_count + 
				(fat_boot->table_count * fat_boot->table_size_16)
			));
	u32int total_clusters = data_sectors / fat_boot->sectors_per_cluster;

	fat_extBS_32_t *extBS_32 = (fat_extBS_32_t*)&fat_boot->extended_section;
	fat_extBS_16_t *extBS_16 = (fat_extBS_16_t*)&fat_boot->extended_section;

	if(total_clusters < 4085) 
	{
		fat_type = 12;
		extBS_16 = (fat_extBS_16_t*)&fat_boot->extended_section;
		first_data_sector = 
					(u32int)(fat_boot->reserved_sector_count + (fat_boot->table_count * fat_boot->table_size_16) + ((fat_boot->root_entry_count * 32) / 512));
	} 
	else 
	{
		if(total_clusters < 65525) 
		{
    		fat_type = 16;
			extBS_16 = (fat_extBS_16_t*)&fat_boot->extended_section;
		} 
		else 
		{
    		fat_type = 32;
			extBS_32 = (fat_extBS_32_t*)&fat_boot->extended_section;

			first_data_sector = 
			fat_boot->reserved_sector_count + (fat_boot->table_count * extBS_32->table_size_32);
			first_fat_sector = fat_boot->reserved_sector_count;
			data_sectors = fat_boot->total_sectors_32 - (fat_boot->reserved_sector_count + (fat_boot->table_count * extBS_32->table_size_32));
			total_clusters = data_sectors / fat_boot->sectors_per_cluster;
		}
	}

	if(fat_type == 16)
	{//for now only support FAT32 and FAT12
		write_string("FAT: FAT");
		write_number(fat_type);
		write_string(" not supported.\n");
		kfree(fat_boot);
		return 0;
	}

	if(fat_type == 32)
	{
		if(extBS_32->boot_signature != 0x29)
		{
			write_string("FAT: signature != 0x29.\n");
			kfree(fat_boot);
			return 0;
		}
	}
	else
	{
		if(extBS_16->boot_signature != 0x29)
		{
			write_string("FAT: signature != 0x29.\n");
			kfree(fat_boot);
			return 0;
		}
	}
	
	//write_char('.');

	//write_char('.');
	//write_string("\nFound a valid FAT");
	//write_number(fat_type);
	//write_string(" volume on partition one.\n");

	//***********************************************//
	//**** find and create a node for each entry ****//
	//***********************************************//
	
	//find the first free root entry
	fat_entry_t *root_entry = 0;
	for(i = 0; i < 4; i++)
	{
		if(!fat_root_exists[i])
		{
			root_entries[i] = (fat_entry_t*)kmalloc(sizeof(fat_entry_t));
			root_entry = root_entries[i];
			fat_root_exists[i] = 1;
			break;
		}
	}

	//write_char('.');

	root_entry->name[0] = '/';
	root_entry->name[1] = 0x00;
	root_entry->long_name[0] = '/';
	root_entry->long_name[1] = 0x00;
	root_entry->attr = FAT_ATTR_DIRECTORY;

	if(fat_type == 32)
	{
		root_entry->cluster = extBS_32->root_cluster;
		root_entry->fat_type = 32;
	}
	else
	{
		//the root entry comes right after the fat table
		root_entry->cluster = (u32int)(fat_boot->reserved_sector_count + (fat_boot->table_count * fat_boot->table_size_16));	
		root_entry->fat_type = 12;
	}

	root_entry->inode = get_valid_inode();
	root_entry->device_node = dev_node;

	//setup some custom attributes about the mountpoint
	root_node->inode = root_entry->inode;
	//point the node's function pointers to the fat file functions
	root_node->read = &fat_read;
   root_node->write = &fat_write;
   root_node->open = &fat_open;
   root_node->close = &fat_close;
	root_node->scan = &fat_scan;
	root_node->dvr_ptr = (u32int*)root_entry;

	//write_string(".\n");

	//parse the root directory (other sub directories will be parsed upon request)
	fat_parse_directory(root_entry, root_node);

	//write_char('.');

	//finally, create a dummy root file handle (never to be used for reading and/or writing)
	root_handle = (fat_file_handle_t*)kmalloc(sizeof(fat_file_handle_t));
	root_handle->node = 0;
	root_handle->entry = 0;
	root_handle->next = 0;

	//clean up after ourselves
	kfree(fat_boot);

	//write_char('.');
	return 1;
}

s32int fat_cluster_is_eoc(u32int active_cluster, fat_entry_t *entry)
{
	if(entry->fat_type == 32)
	{
		if(fat_table_cached_cluster != active_cluster / 128)
		{//table cache miss
			seek_fs(entry->device_node, first_fat_sector + (active_cluster / 128));
			read_fs(entry->device_node, 512, (u8int*)fat_table_cache);
			fat_table_cached_cluster = active_cluster / 128;
		}
		u32int table_value = fat_table_cache[active_cluster % 128] & 0x0FFFFFFF;

		if(table_value >= 0x0FFFFFF8)
		{//this cluster is the last one (aka, we're done now)
			//write_string("eoc");
			return 1;
		}
		else
		{//there is at least one more cluster.
			return 0;
		}
	}
	else
	{
		if(strcmp(entry->name,"/") == 0)//the root directory can not be chained
		{
			//write_string(" Root dir. Can not be chained. ");
			return 1;
		}

		//write_string("End of sector, checking for chain... ");

		u32int fat_offset = active_cluster + (active_cluster / 2);// multiply by 1.5
		u32int fat_sector = first_fat_sector + (fat_offset / 512);
		u32int ent_offset = fat_offset % 512;

		if(fat_table_cached_cluster != fat_sector)
		{//table cache miss
			seek_fs(entry->device_node, fat_sector);
			read_fs(entry->device_node, 512, (u8int*)fat_table_cache);
			fat_table_cached_cluster = fat_sector;
		}
		u8int* tmp_sector = (u8int*)fat_table_cache;
		u16int table_value = *(u16int*)&tmp_sector[ent_offset];

		if(active_cluster & 0x0001)
			table_value = table_value >> 4;
		else
			table_value = (u16int)(table_value & 0x0FFF);

		if(table_value >= 0x0FF8)
		{//this cluster is the last one (aka, we're done now)
			return 1;
		}
		else
		{//there is at least one more cluster.
			return 0;
		}
	}

}

u32int fat_next_in_chain(u32int active_cluster, fat_entry_t *entry)
{
	if(entry->fat_type == 32)
	{
		if(fat_table_cached_cluster != active_cluster / 128)
		{//table cache miss
			seek_fs(entry->device_node, first_fat_sector + (active_cluster / 128));
			read_fs(entry->device_node, 512, (u8int*)fat_table_cache);
			fat_table_cached_cluster = active_cluster / 128;
		}
		u32int table_value = fat_table_cache[active_cluster % 128] & 0x0FFFFFFF;

		u32int offset = (table_value & 0x7F);
		u32int sector = (table_value >> 7);
	
		active_cluster = ((sector * 128) + offset);
	
		//write_number(active_cluster);
		//write_string("->");

		return active_cluster;
	}
	else
	{
		//write_string("following chain... ");
		u32int fat_offset = active_cluster + (active_cluster / 2);// multiply by 1.5
		u32int fat_sector = first_fat_sector + (fat_offset / 512);
		u32int ent_offset = fat_offset % 512;

		if(fat_table_cached_cluster != fat_sector)
		{//table cache miss
			seek_fs(entry->device_node, fat_sector);
			read_fs(entry->device_node, 512, (u8int*)fat_table_cache);
			fat_table_cached_cluster = fat_sector;
		}
		u8int* tmp_sector = (u8int*)fat_table_cache;
		u16int table_value = *(u16int*)&tmp_sector[ent_offset];

		if(active_cluster & 0x0001)
			table_value = table_value >> 4;
		else
			table_value = (u16int)(table_value & 0x0FFF);

		active_cluster = table_value;
		
		return active_cluster;
	}
}

s32int fat_entry_exists_at(u8int *entry)
{
	if(*entry == 0x00)
	{
		return 0;
	}
	if(*entry == 0xE5)
	{
		return 0;
	}

	return 1;
}

s32int fat_entry_is_longfn(u8int *entry)
{
	if(entry[11] == 0x0F)
		return 1;

	return 0;
}

s32int fat_store_name_from_buffer(u8int *entry)
{
	//fill the correct part fat_entry_t's long file name
   u32int long_name_order = (u32int)(*entry & 0x0F);//extract the ordinal
   u32int copypos = (long_name_order - 1) * 14;
   s32int i = 1;
   for(i = 1; i < 10; i+=2)//the name is stored every-other byte
   {
    	tmp_name[copypos] = (s8int)entry[i];
     	copypos++;
   }
   for(i = 14; i < 25; i+=2)//the name is stored every-other byte
   {
     	tmp_name[copypos] = (s8int)entry[i];
     	copypos++;
   }
   for(i = 28; i < 32; i+=2)//the name is stored every-other byte
   {
		tmp_name[copypos] = (s8int)entry[i];
     	copypos++;
   }
	return 1;
}

s32int fat_apply_lfn(fat_entry_t *entry)
{
	s32int i = 0;
	for(i = 0; i < 128; i++)
	{
		entry->long_name[i] = tmp_name[i];
		tmp_name[i] = 0;
	}
	return 1;
}

s32int fat_fill_data_from_buffer(u8int *buffer, fat_entry_t *entry)
{
	s32int i = 0;
	s32int dotpos = 0; 
	s32int ext = 0;	
	for(i = 0; i < 11; i++)
	{
		entry->name[i] = (s8int)buffer[i];
	}
	entry->name[11] = 0x00;

	// copy the short one into the long name for now
	for(i = 0; i < 8; i++)
	{
		if(entry->name[i] == 0x20)
		{
			//do nothing with spaces
		}
		else
		{
			dotpos = i + 2;
			if((entry->name[i] > 64) & (entry->name[i] < 91))
			{
				entry->long_name[i] = (s8int)(entry->name[i] + 32);//copy and convert to lower-case
			}
			else
			{
				//just a plain coy
				entry->long_name[i] = entry->name[i];//copy and convert to lower-case
			}	
		}
	}
	
	for(i = 0; i < 3; i++)
	{
		if(entry->name[i + 8] == 0x20)
		{
			//do nothing with spaces
		}
		else
		{
			ext = 1;
			if((entry->name[i + 8] > 64) & (entry->name[i + 8] < 91))
			{
				entry->long_name[i + dotpos] = (s8int)(entry->name[i + 8] + 32);//copy and convert to lower-case
			}
			else
			{
				//just a plain copy
				entry->long_name[i + dotpos] = entry->name[i + 8];//copy and convert to lower-case
			}	
		}
	}

	if(ext)
		entry->long_name[dotpos - 1] = '.';

	entry->attr = buffer[11];
	entry->time = *(u16int*)(buffer + 14);
	entry->date = *(u16int*)(buffer + 16);

	//the high bytes of the cluster number
	entry->cluster = (u32int)(*(u16int*)(buffer + 20)) << 16;

	//the low bytes of the cluster number
	entry->cluster |= *(u16int*)(buffer + 26);

	entry->size = *(u32int*)(buffer + 28);

	entry->exists = 1;

	return 1;
}

//builds the children of the given directory
s32int fat_parse_directory(fat_entry_t *entry, fs_node_t *node)
{
	s8int end_of_chain = 0;
	s8int lfn = 0;
	s32int i = 0;
	u32int active_cluster = entry->cluster;
	u8int* cluster_buffer = (u8int*)kmalloc(sizeof(s8int) * 512);

	//setup the previous entries. if there are no previous entries then these should be left zeroed
	fat_entry_t *prev_entry = 0;
	fs_node_t *prev_node = 0;
	if(node->first_child)
	{
		fs_node_t *tmp_node = node->first_child;
		while(tmp_node)
		{
			if(tmp_node->next_sibling)
			{
				tmp_node = tmp_node->next_sibling;
			}
			else
			{
				prev_node = tmp_node;
				break;
			}
		}
	}

	write_char('.');
	//write_string("dir '");
	//write_string((s8int*)node->name);
	//write_string("' ");
	//write_number(active_cluster);
	//write_string("\n");

	while(!end_of_chain)
	{
		if(entry->fat_type == 32)
		{
			seek_fs(entry->device_node, active_cluster - 2 + first_data_sector);
			read_fs(entry->device_node, 512, cluster_buffer);
		}
		else
		{
			if(strcmp((const s8int*)node->name,"/") == 0)
			{
				seek_fs(entry->device_node, active_cluster);
				read_fs(entry->device_node, 512, cluster_buffer);
			}
			else
			{
				seek_fs(entry->device_node, active_cluster - 2 + first_data_sector);
				read_fs(entry->device_node, 512, cluster_buffer);
			}
		}

		for(i = 0; i < 16; i++)
		{
			//write_string(" offset: ");
			//write_hex(i * 32);
			//write_string("\n");
			if(fat_entry_exists_at(&cluster_buffer[i * 32]))
			{
				if(fat_entry_is_longfn(&cluster_buffer[i * 32]))
				{
					lfn = 1;
					fat_store_name_from_buffer(&cluster_buffer[i * 32]);
				}
				else
				{
					fat_entry_t *new_entry = (fat_entry_t*)kmalloc(sizeof(fat_entry_t));
					fs_node_t *new_node = (fs_node_t*)kmalloc(sizeof(fs_node_t));

					// IMPORTANT LESSON LEARNED HERE!!! JULY 3, 2008. 
					// ALLWAYS ZERO YOUR NEWLY ALLOCATED MEMORY!!!
					memset(new_entry,0,sizeof(fat_entry_t));				
					memset(new_node,0,sizeof(fs_node_t));	

					new_entry->inode = get_valid_inode();
					new_entry->device_node = entry->device_node;
					new_entry->fat_type = entry->fat_type;

					fat_fill_data_from_buffer(&cluster_buffer[i * 32], new_entry);
					
					if(lfn)
					{
						fat_apply_lfn(new_entry);
						lfn = 0;
					}

					memcpy(new_node->name,new_entry->long_name,128);
					new_node->dvr_ptr = (u32int*)new_entry;
					new_node->dvr_num = 0;		
					new_node->inode = new_entry->inode;
					new_node->length = new_entry->size;
					new_node->ptr = 0;
    							
					if(strcmp((const s8int*)new_node->name,".") == 0)
					{
						//write_string(" dot entry ");
					}
					else if (strcmp((const s8int*)new_node->name,"..") == 0)
					{
						//write_string(" dot dot entry ");
					}
					else if(new_entry->attr == FAT_ATTR_DIRECTORY)
					{
						new_node->flags = FS_DIRECTORY;
					}
					else
					{
						new_node->flags = FS_FILE;
					}
				
					//pos32int the node's function pos32inters to the fat file functions
					new_node->read = &fat_read;
    				new_node->write = &fat_write;
    				new_node->open = &fat_open;
    				new_node->close = &fat_close;
					new_node->scan = &fat_scan;
    				
					new_entry->parent = entry;
					new_node->parent = node;
					
					//write_string("sub '");
					//write_string((s8int*)new_node->name);
					//write_string("'\n");
					//wait_key();

					//if the entry doesn't have a first child
					if(entry->first_child == 0)
					{
						entry->first_child = new_entry;
						prev_entry = entry->first_child;
					}
					else//it alredy has a first child
					{
						prev_entry->next_sibling = new_entry;
						prev_entry = prev_entry->next_sibling;
					}
					if(node->first_child == 0)
					{
						node->first_child = new_node;
						prev_node = node->first_child;
					}
					else
					{
						prev_node->next_sibling = new_node;
						prev_node = prev_node->next_sibling;
					}
				}
			}
		}
		if(fat_cluster_is_eoc(active_cluster, entry))
		{
			end_of_chain = 1;
		}
		else
		{
			active_cluster = fat_next_in_chain(active_cluster, entry);
			end_of_chain = 0;
		}
	}

	kfree(cluster_buffer);

	// This section has been replaced by the scan_fs() mecanism.
	// This means that only the used parts of the file system take up space
	// in the VFS. It saves time at boot up (especially for larger file systems)
	// for a small penalty later.
/*
	//now go through each sibbling entry and, if it is a directory, parse it.
	fs_node_t *tmp_node = node->first_child;
	while(tmp_node != 0)
	{
		//if this entry is a directory (and only a directory)
		if(tmp_node->flags == FS_DIRECTORY)
		{//parse it
			if(tmp_node->dvr_ptr)
			{
				fat_parse_directory((fat_entry_t*)tmp_node->dvr_ptr, tmp_node);
			}		
		}

		tmp_node = tmp_node->next_sibling;
	}
*/
	return 1;
}

s32int fat_scan(fs_node_t* node)
{
	fat_file_handle_t *file_h = fat_get_handle(node, 1);//go ahead and create a handle,
																		// it will be freed before the function exits.
																		// This avoids the need to open a node before it can be scanned.
	if(file_h == 0)
		return 0;//return error for a null pointer

	fat_parse_directory(file_h->entry, file_h->node); // This is where the actuall scanning happens.

	return fat_remove_handle(file_h);
}

s32int fat_open(fs_node_t *node)
{
	fat_file_handle_t *file_h = fat_get_handle(node, 1);//create a handle
	//note: if a handle has alredy been created for the given node,
	//it will return the alredy created handle
	if(file_h == 0)
		return 0;//return error for a null pointer

	//returns the success or faliure of adding the handle
	return fat_add_handle(file_h);
}

s32int fat_close(fs_node_t *node)
{
	fat_file_handle_t *file_h = fat_get_handle(node, 0);//do not create a handle
	if(file_h == 0)
		return 0;//return error for a null pointer
	//returns the success or faliure of removing the handle
	return fat_remove_handle(file_h);
}

//reads the data from the drive described by the given fs_node_t
s32int fat_read(fs_node_t *node, u32int offset, u32int size, u8int *buffer)
{
	fat_file_handle_t *file_h = fat_get_handle(node, 0);//do not create a handle
	if(file_h == 0)
		return 0;//return error for a null pos32inter

	//if the attributes are anything except zero (regular file) then return an error.
	if(file_h->entry->attr & FAT_ATTR_DIRECTORY)
	{
		return 0;
	}
	if(offset > node->length)
	{
		return 0;
	}

	fat_entry_t *entry = file_h->entry;

	if(size > entry->size)
		size = entry->size + 1;

	//if found, use the handle to read the data
	u8int *tmp_buffer = (u8int*)kmalloc(512);
	s8int end_of_chain = 0;
	u32int active_cluster = entry->cluster;
	u32int bytes_left = size;
	u32int dest_index = 0;

	while(!end_of_chain)
	{
		if(bytes_left > 512)
		{
			seek_fs(entry->device_node, active_cluster - 2 + first_data_sector);
			read_fs(entry->device_node, 512, tmp_buffer);
			memcpy(&buffer[dest_index],tmp_buffer,512);
			dest_index += 512;
			bytes_left -= 512;
		}
		else
		{
			if(bytes_left)
			{
				seek_fs(entry->device_node, active_cluster - 2 + first_data_sector);
				read_fs(entry->device_node, bytes_left, tmp_buffer);
				memcpy(&buffer[dest_index],tmp_buffer, (size_t)bytes_left);
				dest_index = size;
				bytes_left = 0;
			}
		}
		if(fat_cluster_is_eoc(active_cluster, entry))
		{
			end_of_chain = 1;
		}
		else
		{
			active_cluster = fat_next_in_chain(active_cluster, entry);
			end_of_chain = 0;
		}
	}

	kfree(tmp_buffer);
	return 1;
}

//writes the data to the drive described by the given fs_node_t
s32int fat_write(fs_node_t *node, u32int offset, u32int size, u8int *buffer)
{
	fat_file_handle_t *file_h = fat_get_handle(node, 0);//do not create a handle
	if(file_h == 0)
		return 0;//return error for a null pos32inter

	if(size > node->length)
	{
		return 0;
	}
	if(offset > node->length)
	{
		return 0;
	}
	if(!buffer)
	{
		return 0;
	}

	//if found, use the handle to write the data
	return 1;
}

//returns the file handle for the given node, creating a new handle if told
fat_file_handle_t *fat_get_handle(fs_node_t *node, u32int create)
{
	//scan the active handles for one that matches the given node
	fat_file_handle_t *tmp1 = root_handle;
	while(tmp1)	//while we havent fallen off the list
	{
		//does this handle go with the given node?
		if(tmp1->node->inode == node->inode)
		{//it's a match, return it
			return tmp1;
		}
		else
		{
			tmp1 = tmp1->next;		//go to the next on the list
		}
	}

	//we fell off the list
	//were we told to create a handle?
	if(create)
	{//yes, create it
		//the node has a driver-defined pos32inter called dvr_prt
		//this fat driver uses it to pos32int back to the fat entry in memory
		
		fat_entry_t *tmp2 = (fat_entry_t*)node->dvr_ptr;

		//if they have the same inode number then they are matches
		if(tmp2->inode == node->inode)
		{//now create the handle
			fat_file_handle_t *new_handle = 
					(fat_file_handle_t*)kmalloc(sizeof(fat_file_handle_t));
			new_handle->node = node;		//pair it with the given node
			new_handle->entry = tmp2;		//at the current entry
			return new_handle;				//return the newly created handle
		}
		else
		{
			return 0;
		}
	}
	return 0;
}

s32int fat_add_handle(fat_file_handle_t *handle)
{
	fat_file_handle_t *tmp = root_handle;
	while(tmp->next)	//while we havent fallen off the list
		tmp = tmp->next;		//go to the next on the list

	//we have now fallen off the list, add the new handle to the end of the list
	tmp->next = handle;
	
	return 1;

}

s32int fat_remove_handle(fat_file_handle_t *handle)
{
	fat_file_handle_t *tmp = root_handle;
	while(tmp->next != 0)	//while we havent fallen off the list
	{
		//is the next one in the list a match? 
		//(note: this makes it impossible to delete the root)
		if(tmp->next == handle)
		{//it's a match, remove it from the list by linking the one before to the one after 
			fat_file_handle_t *t = tmp->next;
			tmp->next = tmp->next->next; 
			kfree(t);//avoid a memory leak by freeing the handle
			return 1;//success!
		}
		else
		{
			tmp = tmp->next;		//go to the next on the list
		}
	}

	//we fell off the list, return an error
	return 0;
}

